using System;
using System.Collections.Generic;
using Framework.Core;
using UnityEngine;

namespace Framework
{
    public class AssetLoader// : EventDispatcher
    {
        public bool Enabled { get; protected set; }
        protected float UnloadDelayTime = 8f;
        private readonly CustomPool<AssetLoadResult> _assetLoadResultPool = new CustomPool<AssetLoadResult>(() => { return new AssetLoadResult(); }, 30);
        private readonly CustomPool<AssetLoadData> _assetLoadDataPool = new CustomPool<AssetLoadData>(() => { return new AssetLoadData(); }, 50);
        private readonly Dictionary<string, AssetLoadData> _loadAssetMap = new Dictionary<string, AssetLoadData>(100);
        private readonly Queue<AssetLoadData> _addLoadQueue = new Queue<AssetLoadData>(100);
        private readonly List<AssetLoadResult> _waitingResultList = new List<AssetLoadResult>();
        private readonly LinkedList<AssetLoadData> _unloadList = new LinkedList<AssetLoadData>();
        //卸载凭证
        private readonly Dictionary<int, AssetToken> _unloadToken = new Dictionary<int, AssetToken>();
        private readonly Dictionary<int, IAssetResult> _unloadResultToken = new Dictionary<int, IAssetResult>();
        //加载卸载信息 用于分析行为
        private readonly Dictionary<string, AssetLoadInfo> _loadInfo = new(100);
        protected IAssetProcessor _assetProcessor;
        protected int unloadCount;
        private readonly object _sign;
        private ThreadPriority _backgroundLoadingPriority;
        private bool _highThreadPriority;
        private readonly List<AssetLoadResult> _sceneList = new(10);
        private readonly List<AssetLoadResult> _sceneWaitList = new(10);
        private float _realtimeSinceStartup;
        private float _idleTime;
        protected float IdleTime => _idleTime - (Time.realtimeSinceStartup - _realtimeSinceStartup);
        public AssetLoader()
        {
            _sign = _loadInfo;
            _idleTime = 0.01f;
            _backgroundLoadingPriority = ThreadPriority.Normal;
            AssetToken.Init();
        }
        public void SetIdleTime(float idleTime)
        {
            _idleTime = idleTime;
        }
        public void SetAssetProcessor(IAssetProcessor value)
        {
            _assetProcessor = value;
        }
        public void SetUnloadDelayTime(float value)
        {
            this.UnloadDelayTime = value;
        }
        public void SetBackgroundThreadPriority(bool value)
        {
            if (value)
            {
                if (!_highThreadPriority)
                {
                    _highThreadPriority = true;
                    _backgroundLoadingPriority = Application.backgroundLoadingPriority;
                }
                Application.backgroundLoadingPriority = ThreadPriority.High;
            }
            else
            {
                if (_highThreadPriority)
                {
                    _highThreadPriority = false;
                    _highThreadPriority = false;
                    Application.backgroundLoadingPriority = _backgroundLoadingPriority;
                }
            }
        }
        public void Update(float deltaTime, float unscaledDeltaTime)
        {
            _realtimeSinceStartup = Time.realtimeSinceStartup;
            onUpdate(deltaTime, unscaledDeltaTime);
            if (!Enabled) return;
            int len;

            len = _waitingResultList.Count;
            while (len-- > 0)
            {
                var result = _waitingResultList[len];
                var asset = getAssetLoadData(result.Path);
                if (asset == null)
                {
                    _waitingResultList.RemoveAt(len);
                    result.Finish();
                }
                else
                {
                    if (asset.Finished)
                    {
                        asset.ActiveCount--;
                        _waitingResultList.RemoveAt(len);
                        if (_assetProcessor != null)
                        {
                            _assetProcessor.ProcessAsset(asset.Asset, asset.Path);
                        }
                        if (asset.RefCount < 1)
                        {
                            result.Finish();
                        }
                        else
                        {
                            if (asset.AsyncOperationScene != null)
                            {
                                result.Finish(asset.AsyncOperationScene);
                                _sceneWaitList.Add(result);
                            }
                            else
                            {
                                var obj = asset.Asset;
                                if (result.IsGameObject && (obj is GameObject go))
                                {
                                    var ins = GameObject.Instantiate(go);
                                    var autoUnload = ins.AddComponent<DestroyWatcher>();
                                    autoUnload.UnloadToken = result;
                                    autoUnload.Callback = onDestory;
                                    obj = ins;
                                    result.Finish(ins);
                                }
                                else
                                {
                                    result.Finish(obj);
                                }
                            }
                        }
                    }
                }
                if (IdleTime < 0f)
                {
                    //break;
                }
            }
            len = _sceneWaitList.Count;
            while (len-- > 0)
            {
                var scene = _sceneWaitList[len];
                var op = scene.SceneAsset;
                if (op == null) continue;
                if (op.isDone && op.progress >= 1f)
                {
                    _sceneWaitList.RemoveAt(len);
                    if (scene.SceneSingleMode)
                    {
                        //unload other scene
                        var scenelen = _sceneList.Count;
                        while (scenelen-- > 0)
                        {
                            Unload(_sceneList[scenelen]);
                        }
                        _sceneList.Clear();
                    }
                    _sceneList.Add(scene);
                    scene.SceneFinish();
                }
            }
            len = _addLoadQueue.Count;
            while (len-- > 0)
            {
                var loadData = _addLoadQueue.Dequeue();
                loadAsset(loadData);
            }

            unload(false);
        }
        private void onDestory(DestroyWatcher value)
        {
            if (value == null) return;
            this.Unload(value.UnloadToken);
        }
        protected virtual void onUpdate(float deltaTime, float unscaledDeltaTime)
        {

        }

        protected virtual void loadAsset(AssetLoadData value)
        {

        }
        protected virtual void unloadAsset(string path)
        {

        }
        protected void unloadScene(string path)
        {
            var len = _sceneList.Count;
            while (len-- > 0)
            {
                var result = _sceneList[len];
                if (result.Path == path)
                {
                    _sceneList.RemoveAt(len);
                }
            }
            len = _sceneWaitList.Count;
            while (len-- > 0)
            {
                var result = _sceneWaitList[len];
                if (result.Path == path)
                {
                    _sceneWaitList.RemoveAt(len);
                }
            }
        }
        private AssetLoadData getAssetLoadData(string path)
        {
            if (string.IsNullOrEmpty(path)) return null;
            if (_loadAssetMap.TryGetValue(path, out AssetLoadData data))
            {
                return data;
            }
            return null;
        }
        public IAssetResult LoadAsset(string assetPath, Action<IAssetResult> callback = null)
        {
            return load(assetPath, callback);
        }
        public void Unload(IAssetResult result, float delayTime = 0f)
        {
            if (result == null) return;
            string assetPath = string.Empty;
            if (!_unloadResultToken.TryGetValue(result.InstanceId, out IAssetResult token))
            {
                FrameworkLog.LogErrorFormat("卸载资源失败 卸载完不要再保留凭证", result.Path);
                return;
            }
            if (token != result)
            {
                FrameworkLog.LogErrorFormat("卸载资源失败 卸载完不要再保留凭证", result.Path);
                return;
            }
            _unloadResultToken.Remove(result.InstanceId);
            assetPath = result.Path;
            if (_loadAssetMap.TryGetValue(assetPath, out AssetLoadData loadData))
            {
                if (_loadInfo.TryGetValue(assetPath, out AssetLoadInfo info))
                {
                    info.UnloadCount++;
                }
                loadData.RemoveRef(delayTime);
                if (loadData.RefCount < 1)
                {
                    _unloadList.AddLast(loadData);
                    if (loadData.RefCount < 0)
                    {
                        // FrameworkLog.LogErrorFormat(" asset Unload {0}", assetPath);
                    }
                }
            }
            _assetLoadResultPool.Release(result as AssetLoadResult);
        }
        /// <summary>
        /// 立即卸载
        /// </summary>
        public void Unload()
        {
            unload(true);
            if (unloadCount > 0)
            {
                unloadCount = 0;
                //long beforeMem = GC.GetTotalMemory(false);
                //long afterMem = GC.GetTotalMemory(true);
                //FrameworkLog.LogWarningFormat("memBefore :{0} kB memAfter :{1} KB inc:{2} KB", beforeMem / 1024, afterMem / 1024, (afterMem - beforeMem) / 1024);
            }
        }
        /// <summary>
        /// 当前正在使用的资源
        /// </summary>
        /// <param name="action">资源地址，额外信息</param>
        public void GetActiveAssets(Action<string, string> action)
        {
            if (action == null) return;
            foreach (var item in _unloadToken)
            {
                var token = item.Value;
                action.Invoke(token.Path, token.Info);
            }
        }
        private IAssetResult load(string assetPath, Action<IAssetResult> callback = null)
        {
            var result = _assetLoadResultPool.Get();
            result.Begin(assetPath);
            result.SetCompleteCallback(callback);
            if (!string.IsNullOrEmpty(assetPath))
            {
                assetPath = assetPath.Replace("\\", "/");
                if (!_loadInfo.TryGetValue(assetPath, out AssetLoadInfo info))
                {
                    info = new AssetLoadInfo();
                    _loadInfo.Add(assetPath, info);
                    info.StartFrame = Time.frameCount;
                }
                info.LoadCount++;


                if (!_loadAssetMap.TryGetValue(assetPath, out AssetLoadData loadData))
                {
                    loadData = _assetLoadDataPool.Get();
                    loadData.Begin(assetPath);
                    _loadAssetMap.Add(assetPath, loadData);
                    _addLoadQueue.Enqueue(loadData);
                }
                loadData.AddRef();
                loadData.ActiveCount++;
            }
            else
            {
                FrameworkLog.LogError("assetPath is empty ");
            }

            _unloadResultToken.Add(result.InstanceId, result);
            _waitingResultList.Add(result);
            return result;
        }
        private void unload(bool immediate)
        {
            if (_unloadList.Count < 1) return;
            var loadAssetNode = _unloadList.First;
            var nowTime = Time.time;
            while (loadAssetNode != null)
            {
                var next = loadAssetNode.Next;
                var loadAsset = loadAssetNode.Value;
                if (loadAsset.RefCount > 0)
                {
                    _unloadList.Remove(loadAssetNode);
                }
                else
                {
                    if (loadAsset.ActiveCount < 1)
                    {
                        //do unload
                        bool unloadNow = false;
                        if (immediate || loadAsset.IsScene)
                        {
                            unloadNow = true;
                        }
                        else
                        {
                            if (loadAsset.ActiveTime <= 0f)
                            {
                                unloadNow = true;
                            }
                            else
                            {
                                if (nowTime > (loadAsset.ActiveTime + UnloadDelayTime))
                                {
                                    unloadNow = true;
                                }
                            }
                        }
                        if (unloadNow)
                        {
                            _unloadList.Remove(loadAssetNode);
                            _loadAssetMap.Remove(loadAsset.Path);
                            unloadAsset(loadAsset.Path);
                            //FrameworkLog.Log("unload " + loadAsset.Path);
                            _assetLoadDataPool.Release(loadAsset);
                        }
                    }
                }
                loadAssetNode = next;
            }
        }
        public static void AppendString(string value, bool isPath = false, bool clear = false)
        {
            Utils.Text.AppendString(value, isPath, clear);
        }
        public static string GetAppendString()
        {
            return Utils.Text.GetAppendString();
        }

        public class AssetLoadData
        {
            public bool Finished { get; private set; }
            public string Path { get; private set; }
            public object Asset { get; private set; }
            public AsyncOperation AsyncOperationScene { get; private set; }
            public int RefCount { get; private set; }
            public float ActiveTime { get; private set; }
            public bool IsScene { get; private set; }
            public int ActiveCount;

            public void Begin(string path)
            {
                this.Finished = false;
                this.Path = path;
                this.Asset = null;
                RefCount = 0;
                ActiveCount = 0;
                IsScene = path.EndsWith(".unity");
            }
            public void AddRef()
            {
                RefCount++;
                if (RefCount < 1)
                {
                    FrameworkLog.LogErrorFormat("Before AddRef Has Error {0}", Path);
                    RefCount = 1;
                }
            }
            public void RemoveRef(float delayTime)
            {
                RefCount--;
                ActiveTime = Time.time + delayTime;
            }
            public void Finish(object asset)
            {
                this.Finish();
                this.Asset = asset;
            }
            public void Finish(AsyncOperation sceneAsset)
            {
                this.Finish();
                this.AsyncOperationScene = sceneAsset;
            }
            public void Finish()
            {
                this.AsyncOperationScene = null;
                this.Asset = null;
                this.Finished = true;
            }
        }
        private class AssetToken
        {
            private static int _l = int.MaxValue;
            public static void Init()
            {

            }
            public static int GetId()
            {
                return createID();
            }
            public static void ReleaseId(string value)
            {
            }

            private static int createID()
            {
                _l -= UnityEngine.Random.Range(1, 100);
                if (_l < 1)
                {
                    _l = int.MaxValue;
                }
                return _l;
            }
            public string Path;
            public string Info;
            public int StartFrame;
        }
        private class AssetLoadInfo
        {
            public int StartFrame;
            public int LoadCount;
            public int UnloadCount;
        }

    }
}